package com.analyse.stock.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.analyse.stock.enums.AnomalyTypeEnum;
import com.analyse.stock.form.SmaForm;
import com.analyse.stock.model.BoardIndexDailyModel;
import com.analyse.stock.model.FundFlowHistoryModel;
import com.analyse.stock.model.StockKlineDailyModel;
import com.analyse.stock.model.StockKlineModel;
import com.analyse.stock.strategy.indicator.*;
import com.analyse.stock.vo.AnomalVO;
import com.analyse.stock.vo.ProfitLostStatVO;
import com.analyse.stock.vo.StockMinuteData;
import com.analyse.stock.vo.StockPositionVO;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.ta4j.core.*;
import org.ta4j.core.indicators.*;
import org.ta4j.core.indicators.bollinger.BollingerBandsLowerIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsMiddleIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsUpperIndicator;
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
import org.ta4j.core.num.Num;
import org.ta4j.core.rules.CrossedDownIndicatorRule;
import org.ta4j.core.rules.CrossedUpIndicatorRule;
import org.ta4j.core.rules.OverIndicatorRule;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Slf4j
public class StockAnalyzer {

    /**
     * 放量滞涨 逻辑判断
     **/
    public static Set<AnomalVO> detectVolumeStagnation(List<StockKlineDailyModel> stockData,
                                                     int avgDays, double volumeMultiplier, double priceThreshold) {
        Set<AnomalVO> anomalyDates = Sets.newHashSet();
        int size = stockData.size();
        for (int i = avgDays; i < size; i++) {
            boolean currentDateSignal = false;
            // Calculate average volume for the last 'avgDays' days
            BigDecimal avgVolume = BigDecimal.ZERO;
            for (int j = i - avgDays; j < i; j++) {
                avgVolume = avgVolume.add(stockData.get(j).getTradeVolume());
            }
            avgVolume = avgVolume.divide(BigDecimal.valueOf(avgDays),2, RoundingMode.HALF_UP);
            // Get current and previous day's data
            StockKlineModel current = stockData.get(i);
            StockKlineModel previous = stockData.get(i - 1);
            // Check if current volume is significantly higher
            boolean isHighVolume = current.getTradeVolume().compareTo(avgVolume.multiply(new BigDecimal(volumeMultiplier)))>0;
            // Check if price stagnation (price increase below threshold)
            boolean isPriceStagnation = (current.getPrice().subtract(previous.getPrice()))
                    .divide(previous.getPrice(),2,RoundingMode.HALF_UP).multiply(new BigDecimal(100)).compareTo(new BigDecimal(priceThreshold))<0;

            //今天开盘价<=今天收盘价 and 今天的成交量 高于历史3天均值
            boolean highVol = current.getTradeVolume().compareTo(avgVolume)>=0;
            if(highVol && current.getPrice().compareTo(current.getOpenPrice())<=0){
                AnomalVO vo = AnomalVO.me(AnomalyTypeEnum.VOLUME_DOWN.getCode(),DateUtil.format(current.getTradeDate(),DatePattern.NORM_DATE_FORMAT));
                anomalyDates.add(vo);
                currentDateSignal = true;
            }
            // If both conditions are met, record the date
            if (isHighVolume && isPriceStagnation) {
                AnomalVO vo = AnomalVO.me(AnomalyTypeEnum.VOLUME_DOWN.getCode(),DateUtil.format(current.getTradeDate(),DatePattern.NORM_DATE_FORMAT));
                anomalyDates.add(vo);
                currentDateSignal = true;
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),
                        DateUtil.format(current.getTradeDate(),DatePattern.NORM_DATE_FORMAT)));
            }
        }
        return anomalyDates;
    }
    
    /**
     * 股票资金异动，流入或流出判断
     **/
    public static List<AnomalVO> detectFundFlowAnomalies(
            List<FundFlowHistoryModel> fundData, int avgDays, double anomalyMultiplier) {
        List<AnomalVO> anomalyDates = new ArrayList<>();
        int size = fundData.size();
        for (int i = avgDays; i < size; i++) {
            boolean currentDateSignal = false;
            // 计算过去 avgDays 天的主力资金净流入平均值
            BigDecimal avgFundFlow = BigDecimal.ZERO;
            for (int j = i - avgDays; j < i; j++) {
                // 主力资金净流入，正值为流入，负值为流出
                avgFundFlow = avgFundFlow.add(fundData.get(j).getMainAmt());
            }
            avgFundFlow = avgFundFlow.divide(BigDecimal.valueOf(avgDays),2,RoundingMode.HALF_UP);
            // 获取当前数据
            FundFlowHistoryModel current = fundData.get(i);
            // 判断是否为资金异动
            boolean isAnomalous = Math.abs(current.getMainAmt().doubleValue()) > Math.abs(avgFundFlow.doubleValue()) * anomalyMultiplier;
            if (isAnomalous) {
                String anomalyType = current.getMainAmt().compareTo(new BigDecimal(0))>0?
                        AnomalyTypeEnum.FUND_FLOW_IN.getCode():AnomalyTypeEnum.FUND_FLOW_OUT.getCode();
                anomalyDates.add(AnomalVO.me(anomalyType,DateUtil.format(current.getTradeDate(),DatePattern.NORM_DATE_FORMAT)));
                currentDateSignal = true;
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),
                        DateUtil.format(current.getTradeDate(),DatePattern.NORM_DATE_FORMAT)));
            }
        }
        return anomalyDates;
    }
    
    /**
     * 股票上长上影 判断
     **/
    public static List<AnomalVO> detectLongUpperShadows(
            List<StockKlineDailyModel> stockData, double shadowMultiplier, double shadowProportion) {
        List<AnomalVO> anomalyDates = new ArrayList<>();
        int period = 10;
        for (int i = period;i<stockData.size(); i++) {
            StockKlineDailyModel dailyModel = stockData.get(i);
            boolean currentDateSignal = false;
            double close = dailyModel.getPrice().doubleValue();
            //最高价- (开盘和收盘价中最大的一个)
            BigDecimal upperShadow = dailyModel.getHighPrice().subtract(
                    BigDecimal.valueOf(Math.max(dailyModel.getOpenPrice().doubleValue(), dailyModel.getPrice().doubleValue())));
            //收盘价-开盘价<0
            double body = Math.abs(dailyModel.getPrice().doubleValue() - dailyModel.getOpenPrice().doubleValue());
            BigDecimal totalRange = dailyModel.getHighPrice().subtract(dailyModel.getLowerPrice());
            boolean isLongUpperShadow = upperShadow.doubleValue() > body * shadowMultiplier
                    && upperShadow.doubleValue()/totalRange.doubleValue() > shadowProportion;
            // 判断是否在高位
            double threshold = getHighestClosePrice(stockData,i,period) * 0.90; // 设定为 90% 的高位
            if (close > threshold) {
                if (isLongUpperShadow) {
                    anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.LONG_UPPER_SHADOW.getCode(),
                            DateUtil.format(dailyModel.getTradeDate(), DatePattern.NORM_DATE_FORMAT)));
                    currentDateSignal = true;
                }
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),
                        DateUtil.format(dailyModel.getTradeDate(),DatePattern.NORM_DATE_FORMAT)));
            }
        }
        return anomalyDates;
    }

    /**
     * 高位 股票长上影判断
     **/
    public static List<AnomalVO> detectLongUpperShadows(
            List<StockKlineDailyModel> stockData, double shadowMultiplier,int lookbackPeriod) {
        // 构建时间序列
        BarSeries series = createSeries(stockData,"LongUpper-Series");
        List<AnomalVO> anomalyDates = new ArrayList<>();
        if(Objects.isNull(series)){
            return anomalyDates;
        }
        // 设置高位的阈值，这里以前 20 个交易日的最高价作为参考
        //int lookbackPeriod = 20;
        // 判断高位长上影线
        for (int i = 1; i < series.getBarCount(); i++) {
            boolean currentDateSignal = false;
            Bar currentBar = series.getBar(i);
            double high = currentBar.getHighPrice().doubleValue();
            double low = currentBar.getLowPrice().doubleValue();
            double open = currentBar.getOpenPrice().doubleValue();
            double close = currentBar.getClosePrice().doubleValue();
            LocalDateTime date = series.getBar(i).getEndTime().toLocalDateTime();
            String dateStr = DateUtil.format(date, DatePattern.NORM_DATE_FORMAT.getPattern());
            // 判断是否在高位
            double threshold = getHighestClosePrice(stockData,i,lookbackPeriod) * 0.90; // 设定为 90% 的高位
            if (close > threshold) {
                // 计算上影线长度和实体部分的比例
                double upperShadow = high - Math.max(open, close); // 上影线长度
                double body = Math.abs(open - close); // 实体长度
                // 判断是否为长上影线：上影线 > 2 * 实体长度
                if (upperShadow > shadowMultiplier * body) {
                    log.info("高位长上影线： " + currentBar.getEndTime());
                    currentDateSignal = true;
                    anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.LONG_UPPER_SHADOW.getCode(),dateStr));
                }
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),dateStr));
            }
        }
        return anomalyDates;
    }
    // 获取过去 n 天的最高收盘价
    private static double getHighestClosePrice(List<StockKlineDailyModel> stockData,int i, int period) {
        double highestClose = 0.0;
        for (int j = i - period; j < i; j++) {
            // 主力资金净流入，正值为流入，负值为流出
            highestClose = Math.max(highestClose, stockData.get(j).getPrice().doubleValue());
        }
        return highestClose;
    }

    /**
     * 定义 RSI 超买规则（RSI > 85）
     * 卖出点信号
     */
    public static List<AnomalVO> detectRSISellRule(List<StockKlineDailyModel> stockData,Integer shortDay,Integer longDay){
        // 构建时间序列
        return detectRSI(stockData, shortDay, longDay, 80, 80,
                "卖出信号触发！时间点：{}，RSI_6值：{},RSI_12值：{}", AnomalyTypeEnum.RSI_OVER_BUY);
    }

    /**
     * 定义 RSI 超卖规则（RSI1 < 30 AND RSI2 < 30）
     * 买入点信号
     */
    public static List<AnomalVO> detectRSIBuyRule(List<StockKlineDailyModel> stockData,Integer shortDay,Integer longDay){
        return detectRSI(stockData, shortDay, longDay, 20, 20,
                "## 买入出信号触发！时间点：{}，RSI_6值：{},RSI_12值：{}", AnomalyTypeEnum.RSI_OVER_SELL);
    }
    /**
     * 创建模拟时间序列
     * @return 时间序列
     */
    public static BarSeries createSeries(List<StockKlineDailyModel> stockData,String seriesName) {
        // 构建时间序列
        BarSeries series = new BaseBarSeriesBuilder().withName(seriesName).build();
        for(StockKlineDailyModel model : stockData){
            // 将java.util.Date转换为java.time.ZonedDateTime
            ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(model.getTradeDate().toInstant(), ZoneId.systemDefault());
            series.addBar(zonedDateTime, model.getOpenPrice(),
                    model.getHighPrice(), model.getLowerPrice(), model.getPrice(), model.getTradeVolume());
        }
        return series;
    }
    public static BarSeries createIndexSeries(List<BoardIndexDailyModel> stockData, String seriesName) {
        // 构建时间序列
        BarSeries series = new BaseBarSeriesBuilder().withName(seriesName).build();
        if(CollUtil.isEmpty(stockData)){
            return series;
        }
        for(BoardIndexDailyModel model : stockData){
            // 将java.util.Date转换为java.time.ZonedDateTime
            ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(model.getTradeDate().toInstant(), ZoneId.systemDefault());
            series.addBar(zonedDateTime, model.getOpenPrice(),
                    model.getHighPrice(), model.getLowerPrice(), model.getPrice(), model.getTradeVolume());
        }
        return series;
    }
    private static List<AnomalVO> detectRSI(List<StockKlineDailyModel> stockData, Integer shortDay, Integer longDay,
                                               Integer rsi1, Integer rsi2, String s, AnomalyTypeEnum typeEnum) {
        // 构建时间序列
        BarSeries series = createSeries(stockData,"RSI-Series");
        List<AnomalVO> anomalyDates = new ArrayList<>();
        if(Objects.isNull(series)){
            return anomalyDates;
        }
        // 计算 RSI 指标
        ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
        RSIIndicator rsi_6 = new RSIIndicator(closePrice, shortDay); // 6 天 RSI
        RSIIndicator rsi_12 = new RSIIndicator(closePrice, longDay); // 12 天 RSI

        // 定义 RSI 超买规则（RSI > 85）
        Rule sellRule_6 = new OverIndicatorRule(rsi_6, series.numOf(rsi1));
        Rule sellRule_12 = new OverIndicatorRule(rsi_12, series.numOf(rsi2));

        // 检查卖出信号
        for (int i = 0; i < series.getBarCount(); i++) {
            boolean currentDateSignal = false;
            LocalDateTime date = series.getBar(i).getEndTime().toLocalDateTime();
            String dateStr = DateUtil.format(date, DatePattern.NORM_DATE_FORMAT.getPattern());
            if (sellRule_6.isSatisfied(i) && sellRule_12.isSatisfied(i)) {
                anomalyDates.add(AnomalVO.me(typeEnum.getCode(), dateStr));
                log.info(s, series.getBar(i), rsi_6.getValue(i), rsi_12.getValue(i));
                currentDateSignal = true;
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),dateStr));
            }
        }
        return anomalyDates;
    }

    /**
     * sma 移动平均线
     */
    public static boolean detectSMA(List<StockKlineDailyModel> stockData, SmaForm smaForm){
        if(Objects.isNull(smaForm) || smaForm.getPeriod().length<3){
            smaForm.setPeriod(new Integer[]{5, 10, 15});
        }
        // 构建时间序列
        BarSeries series = createSeries(stockData,"SMA-Series");
        if(Objects.isNull(series)){
            return false;
        }
        // 使用收盘价计算短期和长期 SMA
        ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
        SMAIndicator shortTermSMA = new SMAIndicator(closePrice, smaForm.getPeriod()[0]); // 短期 SMA (5 天)
        SMAIndicator midTermSMA = new SMAIndicator(closePrice, smaForm.getPeriod()[1]); // 长期 SMA (10 天)
        SMAIndicator longTermSMA = new SMAIndicator(closePrice, smaForm.getPeriod()[2]); // 长期 SMA (10 天)
        Rule shortUpMidRule = new CrossedUpIndicatorRule(shortTermSMA, midTermSMA);
        Rule midUpLongRule = new CrossedUpIndicatorRule(midTermSMA, longTermSMA);
        boolean result = false;


        // 条件 1: 均线排列
        int index = series.getEndIndex();
        boolean isBullishArrangement = true;
        // 条件 2: 均线斜率向上
        boolean isSlopePositive = true;

        LocalDateTime date = series.getBar(series.getEndIndex()).getEndTime().toLocalDateTime();
        String dateStr = DateUtil.format(date, DatePattern.NORM_DATE_FORMAT.getPattern());
        if(smaForm.isShortUpMid()) {
            isBullishArrangement = shortTermSMA.getValue(index).isGreaterThan(midTermSMA.getValue(index));
            isSlopePositive = shortTermSMA.getValue(index).isGreaterThanOrEqual(shortTermSMA.getValue(index - 1))
                    && midTermSMA.getValue(index).isGreaterThanOrEqual(midTermSMA.getValue(index - 1));
        }
        /*log.info("时间点：{}, 短期SMA{}：{}, 中期SMA{}：{}",dateStr,smaForm.getPeriod()[0],
                shortTermSMA.getValue(series.getEndIndex()),smaForm.getPeriod()[1], midTermSMA.getValue(series.getEndIndex()));*/
        if(smaForm.isMidUpLong()){
            isBullishArrangement = midTermSMA.getValue(index).isGreaterThanOrEqual(longTermSMA.getValue(index));
            isSlopePositive = midTermSMA.getValue(index).isGreaterThanOrEqual(midTermSMA.getValue(index - 1))
                    && longTermSMA.getValue(index).isGreaterThanOrEqual(longTermSMA.getValue(index - 1));
        }
        /*log.info("时间点：{}, 中期SMA{}：{}, 长期SMA{}：{}",dateStr,smaForm.getPeriod()[1],
                midTermSMA.getValue(series.getEndIndex()),smaForm.getPeriod()[2], longTermSMA.getValue(series.getEndIndex()));*/
        return isBullishArrangement && isSlopePositive;
    }

    /**
     * MACD 信号点
     */
    public static List<AnomalVO> detectMACD(List<StockKlineDailyModel> stockData, Integer shortDay, Integer longDay){
        // 构建时间序列
        BarSeries series = createSeries(stockData,"MACD-Series");
        List<AnomalVO> anomalyDates = new ArrayList<>();
        if(Objects.isNull(series)){
            return anomalyDates;
        }
        // 使用收盘价计算 MACD 和信号线
        ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
        // 快速 EMA: 12, 慢速 EMA: 26
        MACDIndicator macd = new MACDIndicator(closePrice, shortDay, longDay);
        // 信号线的 EMA: 9
        EMAIndicator signalLine = new EMAIndicator(macd, 9);

        // 定义买入信号规则（MACD 上穿信号线）
        Rule buyRule = new CrossedUpIndicatorRule(macd, signalLine);

        // 定义卖出信号规则（MACD 下穿信号线）
        Rule sellRule = new CrossedDownIndicatorRule(macd, signalLine);

        // 检查买入和卖出信号
        for (int i = 0; i < series.getBarCount(); i++) {
            boolean currentDateSignal = false;
            if (!(sellRule.isSatisfied(i) || buyRule.isSatisfied(i))) {
                continue;
            }
            LocalDateTime date = series.getBar(i).getEndTime().toLocalDateTime();
            String dateStr = DateUtil.format(date, DatePattern.NORM_DATE_FORMAT.getPattern());
            if (sellRule.isSatisfied(i)) {
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.MACD_DEATH_FORK.getCode(), dateStr));
                log.info("卖出信号触发！时间点：" + i + ", MACD 值：" + macd.getValue(i) +", 信号线值：" + signalLine.getValue(i));
                currentDateSignal = true;
            }
            if (buyRule.isSatisfied(i)) {
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.MACD_GOLD_FORK.getCode(), dateStr));
                log.info("买入信号触发！时间点：" + i + ", MACD 值：" + macd.getValue(i) +", 信号线值：" + signalLine.getValue(i));
                currentDateSignal = true;
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),dateStr));
            }
        }
        return anomalyDates;
    }

    /**
     * BOLL 信号点
     * 使用可视化工具（如 JFreeChart）绘制布林带及其信号点
     */
    public static List<AnomalVO> detectBOLL(List<StockKlineDailyModel> stockData){
        // 构建时间序列
        BarSeries series = createSeries(stockData,"BOLL-Series");
        List<AnomalVO> anomalyDates = new ArrayList<>();
        if(Objects.isNull(series)){
            return anomalyDates;
        }
        // 使用收盘价计算 BOLL 指标
        ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
        // 中轨线 (20 天均线)
        SMAIndicator sma = new SMAIndicator(closePrice, 20);
        // 标准差
        BollingerBandsMiddleIndicator deviation = new BollingerBandsMiddleIndicator(sma);
        //上轨线 （中轨线 + 2 * 标准差）
        BollingerBandsUpperIndicator upperBand = new BollingerBandsUpperIndicator(
                deviation,sma, deviation.getBarSeries().numOf(2));
        // 下轨线 （中轨线 - 2 * 标准差）
        BollingerBandsLowerIndicator lowerBand = new BollingerBandsLowerIndicator(
                deviation,sma,deviation.getBarSeries().numOf(2));

        // 定义买入信号规则（价格下穿下轨线）  买入信号：当收盘价下穿下轨线时触发。
        Rule buyRule = new CrossedDownIndicatorRule(closePrice, lowerBand);

        // 定义卖出信号规则（价格上穿上轨线）  卖出信号：当收盘价上穿上轨线时触发
        Rule sellRule = new CrossedUpIndicatorRule(closePrice, upperBand);
        // 检查买入和卖出信号
        for (int i = 0; i < series.getBarCount(); i++) {
            boolean currentDateSignal = false;
            if (!(sellRule.isSatisfied(i) || buyRule.isSatisfied(i))) {
                continue;
            }
            LocalDateTime date = series.getBar(i).getEndTime().toLocalDateTime();
            String dateStr = DateUtil.format(date, DatePattern.NORM_DATE_FORMAT.getPattern());
            if (buyRule.isSatisfied(i)) {
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.BOLL_GOLD_FORK.getCode(), dateStr));
                log.info("买入信号触发！时间点：" + i +", 收盘价：" + closePrice.getValue(i) +", 下轨线值：" + lowerBand.getValue(i));
                currentDateSignal = true;
            }
            if (sellRule.isSatisfied(i)) {
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.BOLL_DEATH_FORK.getCode(), dateStr));
                log.info("卖出信号触发！时间点：" + i +", 收盘价：" + closePrice.getValue(i) +", 上轨线值：" + upperBand.getValue(i));
                currentDateSignal = true;
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),dateStr));
            }
        }
        return anomalyDates;
    }

    /**
     * KD信号点
     */
    public static List<AnomalVO> detectKD(List<StockKlineDailyModel> stockData){
        // 构建时间序列
        BarSeries series = createSeries(stockData,"KD-Series");
        List<AnomalVO> anomalyDates = new ArrayList<>();
        if(Objects.isNull(series)){
            return anomalyDates;
        }

        // 创建 KDJ 指标（%K、%D）
        StochasticOscillatorKIndicator k = new StochasticOscillatorKIndicator(series, 14);
        // D 线：K 线的 3 日均线
        //SMAIndicator dIndicator2 = new SMAIndicator(k, 3);
        StochasticOscillatorDIndicator d = new StochasticOscillatorDIndicator(k);

        // 判断买卖信号
        for (int i = 1; i < series.getBarCount(); i++) {
            boolean currentDateSignal = false;
            Bar currentBar = series.getBar(i);
            Num currentK = k.getValue(i);
            Num currentD = d.getValue(i);
            // 手动计算 J线
            Num currentJ = currentK.multipliedBy(series.numOf(3)).minus(currentD.multipliedBy(series.numOf(2)));
            // 计算前一天的 K 和 D
            Num previousK = k.getValue(i - 1);
            Num previousD = d.getValue(i - 1);

            LocalDateTime date = series.getBar(i).getEndTime().toLocalDateTime();
            String dateStr = DateUtil.format(date, DatePattern.NORM_DATE_FORMAT.getPattern());
            // 判断超买超卖区域 K 穿 D
            if (currentK.isGreaterThan(currentD) && previousK.isLessThan(previousD) &&
                    currentJ.isLessThan(series.numOf(20))) {
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.KD_GOLD_FORK.getCode(), dateStr));
                log.info("## 金叉, 超卖区域（可能买入信号）： " + currentBar.getEndTime());
                currentDateSignal = true;
            } else if (currentK.isLessThan(currentD) && previousK.isGreaterThan(previousD) &&
                    currentJ.isGreaterThan(series.numOf(80))) {
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.KD_DEATH_FORK.getCode(), dateStr));
                log.info("## 死叉, 超买区域（可能卖出信号）： " + currentBar.getEndTime());
                currentDateSignal = true;
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),dateStr));
            }
        }
        return anomalyDates;
    }

    /**
     * 我们可以通过计算 SAR（Stop and Reverse）指标 来判断股票的买卖信号。
     * SAR 指标通常用于识别趋势反转点，它是一种基于价格和加速因子的动态止损指标，通常用于趋势交易策略中
     *
     * SAR 指标概述
     * SAR 的计算公式和策略较为复杂，简要地说，它会随着趋势的变化逐渐向价格移动，趋势反转时，SAR 会突然改变方向。一般情况下：
     * 当价格在上升趋势时，SAR 会在价格的下方，表示做多信号。
     * 当价格在下降趋势时，SAR 会在价格的上方，表示做空信号。
     **/
    public static List<AnomalVO> detectSAR(List<StockKlineDailyModel> stockData){
        // 构建时间序列
        BarSeries series = createSeries(stockData,"SAR-Series");
        List<AnomalVO> anomalyDates = new ArrayList<>();
        if(Objects.isNull(series)){
            return anomalyDates;
        }
        // 设置 SAR 指标：加速因子 (af) 为 0.02，最大值为 0.2
        ParabolicSarIndicator sar = new ParabolicSarIndicator(
                series, series.numOf(0.05), series.numOf(0.2));
        // 判断买卖信号
        for (int i = 1; i < series.getBarCount(); i++) {
            boolean currentDateSignal = false;
            Bar currentBar = series.getBar(i);
            Bar previousBar = series.getBar(i - 1);
            Num currentSar = sar.getValue(i);
            Num previousSar = sar.getValue(i - 1);
            LocalDateTime date = series.getBar(i).getEndTime().toLocalDateTime();
            String dateStr = DateUtil.format(date, DatePattern.NORM_DATE_FORMAT.getPattern());
            if (currentBar.getClosePrice().isGreaterThan(currentSar) && previousBar.getClosePrice().isLessThan(previousSar)) {
                log.info("## SAR买入信号： {}" , currentBar.getEndTime());
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.SAR_BUY.getCode(), dateStr));
                currentDateSignal = true;
            } else if (currentBar.getClosePrice().isLessThan(currentSar) && previousBar.getClosePrice().isGreaterThan(previousSar)) {
                log.info("## SAR 卖出信号： {}" , currentBar.getEndTime());
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.SAR_SELL.getCode(), dateStr));
                currentDateSignal = true;
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),dateStr));
            }
        }
        return anomalyDates;
    }

    /**
     * stockData的日期不能断开
     * calcDays 前两天，再加今天，总共就3天
     **/
    public static List<AnomalVO> moveCalcTrend(List<AnomalVO> anomalyDates,Integer calcDays) {
        if(CollUtil.isEmpty(anomalyDates)){
            return anomalyDates;
        }
        //结果处理
        Map<String, String> anomalyDateMaps = anomalyDates.stream().collect(Collectors.groupingBy(
                AnomalVO::getDate,
                Collectors.mapping(AnomalVO::getAnomalyType,Collectors.joining(StrUtil.COMMA))));
        List<AnomalVO> anomalyResult = Lists.newArrayList();
        anomalyDateMaps.forEach((date,types)->anomalyResult.add(AnomalVO.me(types,date)));
        anomalyResult.sort(Comparator.comparing(AnomalVO::getDate));

        //利用权重进行 移动平均取值
        int size = anomalyResult.size();
        for (int i = 0; i < size; i++) {
            Integer calcVal = 0;
            AnomalVO currentVO = anomalyResult.get(i);
            currentVO.setCalcWeightVal(calcVal);
            if(i<calcDays){
                continue;
            }
            for (int j = i - calcDays; j < i; j++) {
                calcVal += anomalyResult.get(j).getWeightVal();
            }
            currentVO.setCalcWeightVal(calcVal+(currentVO.getWeightVal()));
        }
        int ts = anomalyResult.size()/10;
        //计算完后，返回前 (交易日/10) 个最高和前 (交易日/10) 的最低
        List<AnomalVO> sortAscList = anomalyResult.stream().sorted(Comparator.comparing(AnomalVO::getCalcWeightVal)).collect(Collectors.toList());
        List<AnomalVO> lowerList = sortAscList.size()>ts?sortAscList.subList(0,ts):sortAscList;
        List<AnomalVO> sortDescList = anomalyResult.stream().sorted(Comparator.comparing(AnomalVO::getCalcWeightVal).reversed()).collect(Collectors.toList());
        List<AnomalVO> higList = sortDescList.size()>ts?sortDescList.subList(0,ts):sortDescList;
        Set<AnomalVO> sets = Sets.newHashSet();
        sets.addAll(lowerList.stream().filter(e->e.getCalcWeightVal()<0).collect(Collectors.toList()));
        sets.addAll(higList.stream().filter(e->e.getCalcWeightVal()>0).collect(Collectors.toList()));
        List<AnomalVO> result = new ArrayList<>(sets);
        return result.stream().sorted(Comparator.comparing(AnomalVO::getDate)).collect(Collectors.toList());
    }

    public static Integer detectTrend(List<StockKlineDailyModel> stockData){
        // 构建时间序列
        BarSeries series = createSeries(stockData,"Trend-Series");
        if(Objects.isNull(series)){
            return 0;
        }
        // 移动平均线指标：短期（5 日）和长期（10 日）
        ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
        SMAIndicator shortSma = new SMAIndicator(closePrice, 5); // 短期均线
        SMAIndicator longSma = new SMAIndicator(closePrice, 10); // 长期均线

        // 判断趋势：短期均线 vs 长期均线
        int endIndex = series.getEndIndex();
        int result = 0;
        if (shortSma.getValue(endIndex).isGreaterThan(longSma.getValue(endIndex))) {
            System.out.println("移动平均线指标 当前趋势：上升趋势");
            result = 1;
        } else if (shortSma.getValue(endIndex).isLessThan(longSma.getValue(endIndex))) {
            System.out.println("移动平均线指标 当前趋势：下降趋势");
            result = -1;
        } else {
            System.out.println("移动平均线指标 当前趋势：横盘震荡");
        }
        // 输出 SMA 值（调试信息）
        System.out.println("短期均线 (SMA-5): " + shortSma.getValue(endIndex));
        System.out.println("长期均线 (SMA-10): " + longSma.getValue(endIndex));
        return result;
    }

    public static void testFundFlowAnomalyIndicator(List<StockKlineDailyModel> stockData){
        // 构建时间序列
        BarSeries series = createSeries(stockData,"SAR-Series");
        List<AnomalVO> anomalyDates = new ArrayList<>();
        if(Objects.isNull(series)){
            return;
        }
        // 创建主力净流入资金指标
        //NetCapitalFlowIndicator netCapitalFlowIndicator = new NetCapitalFlowIndicator(series);

        // 创建主力资金异动指标
        FundFlowAnomalyIndicator anomalyIndicator = new FundFlowAnomalyIndicator(null, 3, 50);

        // 遍历时间序列，输出异动值
        for (int i = 0; i < series.getBarCount(); i++) {
            System.out.println("索引 " + i + " 的主力资金异动值: " + anomalyIndicator.getValue(i));
        }
    }
    // 帮助方法：创建带主力资金的 Bar
    private static Bar createBar(double closePrice, double netCapitalFlow) {
        Bar bar = new BaseBar(Duration.ofDays(1), ZonedDateTime.now(), 0, 0, 0, closePrice, 0);
        //bar.("netCapitalFlow", netCapitalFlow);
        return bar;
    }


    /**
     * =====国内全市场5000多只股票=======
     * 数据区间：从2024-01-01  ~ 2024-11-29
     * 开单规则：
     * 1、一买一卖，不补仓，持仓不超过3只。
     * 2、单笔上限2w,可调整。
     * 3、止盈，当当前股票从盈利最高，回撤6%时止盈卖出。
     * 4、止损，当当前股票亏损达-4%时触发止损。
     *
     * 开单策略：
     * WR下穿50~20区间  + 多头均线向上突破排列  + 现价站上5日均线
     * SM5 日线斜率 >0.3
     * SM10 日线斜率 >0.1
     * 买入点没有长上影线 ,上影线长度 超过（最高价-最低价）的一半
     * 当日成交量3日平均 出现2倍量时，直接买入。
     *
     * 做空策略（满足其一做空）：
     * WR向上穿20 并且 跌破5日线
     * 跌破10日线触发卖出
     * 当天振幅 >7.5%  并且是收绿（现价<开盘价）
     * 均线多头排列破坏
     * 现价跌破十日均线
     */
    public static ProfitLostStatVO detectWR(List<StockKlineDailyModel> stockData, int wrPeriod,SMAIndicator indicator){
        // 构建时间序列
        BarSeries series = createSeries(stockData,"WR-Series");
        List<AnomalVO> anomalyDates = new ArrayList<>();
        if(Objects.isNull(series)){
            return null;
        }
        // 创建 WR 指标
        // 创建新的 WR 指标（值范围 0 到 100）
        AdjustedWilliamsRIndicator adjustedWRIndicator = new AdjustedWilliamsRIndicator(series, wrPeriod);
        //下穿50 买入信号
        CrossedDownIndicatorRule down20 = new CrossedDownIndicatorRule(adjustedWRIndicator, series.numOf(23));
        CrossedDownIndicatorRule down52 = new CrossedDownIndicatorRule(adjustedWRIndicator, series.numOf(52.5));
        Rule buyingRule = down52.or(down20);  // WR < 50 表示超卖，买入信号
        double step = 0.25d;
        double val = 0d;
        AtomicInteger atomicInt = new AtomicInteger(0);
        double startVal = 8.0d;
        while(val<=52.5){
            atomicInt.incrementAndGet();
            val = startVal + (startVal * atomicInt.get() * step);
            buyingRule = buyingRule.or(new CrossedDownIndicatorRule(adjustedWRIndicator, series.numOf(val)));
        }

        // WR < 50 表示超卖，买入信号;   // WR > 23 表示超买，卖出信号
        CrossedUpIndicatorRule sell22 = new CrossedUpIndicatorRule(adjustedWRIndicator, series.numOf(22));
        CrossedUpIndicatorRule sell52 = new CrossedUpIndicatorRule(adjustedWRIndicator, series.numOf(52));
        Rule sellingRule = sell22.or(sell52);
        double startSellVal = 47d;
        AtomicInteger sellInt = new AtomicInteger(0);
        double sellVal = 0;
        double sellStep = 0.05d;
        while(sellVal<=52){
            sellInt.incrementAndGet();
            sellVal = startSellVal + (startSellVal * sellInt.get() * sellStep);
            sellingRule = sellingRule.or(new CrossedUpIndicatorRule(adjustedWRIndicator, series.numOf(sellVal)));
        }
        // 创建策略
        Strategy strategy = new BaseStrategy(buyingRule, sellingRule);

        // 收盘价、最低价和最高价指标
        ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
        // 计算 K、D、J 值
        KDJIndicator kdj = new KDJIndicator(series, 9);
        // 使用收盘价计算短期和长期 SMA
        //ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
        SMAIndicator shortTermSMA = new SMAIndicator(closePrice, 5); // 短期 SMA (5 天)
        SMAIndicator midTermSMA = new SMAIndicator(closePrice, 10); // 长期 SMA (10 天)
        SMAIndicator longTermSMA = new SMAIndicator(closePrice, 15); // 长期 SMA (15 天)

        String stockCode = stockData.get(0).getStockCode();
        Map<String, StockPositionVO> positionMap = Maps.newHashMap();

        // 创建 SMA Slope 指标，SM5,SM10斜率判断
        int slopePeriod = 1; // 斜率计算间隔
        SMASlopeIndicator smaSlopeSM5 = new SMASlopeIndicator(closePrice, 5, slopePeriod);
        SMASlopeIndicator smaSlopeSM10 = new SMASlopeIndicator(closePrice, 10, slopePeriod);

        //判断买入点是否有  长影线阈值 50%
        double shadowThreshold = 0.5;
        // 创建长影线指标
        LongShadowIndicator longShadowIndicator = new LongShadowIndicator(series, shadowThreshold);

        // 创建规则：基于 3 日均值判断
        Rule volumeRule = new VolumeSMAIndicator(series, 3);

        BigDecimal amt = BigDecimal.ZERO;
        Integer sellTimes = 0;
        Integer profitTimes = 0;
        ProfitLostStatVO profitLostStatVO = new ProfitLostStatVO();
        profitLostStatVO.setAmt(BigDecimal.ZERO);
        // 遍历序列，判断买卖信号
        for (int i = 0; i < series.getBarCount(); i++) {
            LocalDateTime date = series.getBar(i).getEndTime().toLocalDateTime();
            String dateStr = DateUtil.format(date, DatePattern.NORM_DATE_FORMAT.getPattern());
            if(i<wrPeriod){
                continue;
            }
            boolean currentDateSignal = false;

            // 条件 1: 均线排列
            boolean isBullishArrangement = shortTermSMA.getValue(i).isGreaterThan(midTermSMA.getValue(i))
                    && midTermSMA.getValue(i).isGreaterThanOrEqual(longTermSMA.getValue(i));

            // 条件 2: 均线斜率向上
            boolean isSlopePositive = shortTermSMA.getValue(i).isGreaterThanOrEqual(shortTermSMA.getValue(i - 1))
                    && midTermSMA.getValue(i).isGreaterThanOrEqual(midTermSMA.getValue(i - 1))
                    && longTermSMA.getValue(i).isGreaterThanOrEqual(longTermSMA.getValue(i - 1));

            /*System.out.printf("### 日期%s , Bar %d: SMA = %.2f, Slope = %.4f%n",dateStr,
                    i,smaSlopeSM10.getBarSeries().numOf(10).doubleValue(),smaSlopeSM10.getValue(i).doubleValue());*/

            //实时计算，及时止损
            BigDecimal amtVal2 = StockPositionVO.calc(positionMap.get(stockCode),series.getBar(i));
            if(amtVal2.compareTo(BigDecimal.ZERO)!=0){
                positionMap.remove(stockCode);
                amt = amt.add(amtVal2);
                sellTimes += 1;
                if(Objects.nonNull(positionMap.get(stockCode)) &&
                        positionMap.get(stockCode).getAmt().compareTo(profitLostStatVO.getAmt())>0){
                    profitLostStatVO.setAmt(positionMap.get(stockCode).getAmt());
                }
                if(amtVal2.compareTo(BigDecimal.ZERO)>0){
                    profitTimes += 1;
                }
            }

            //已经开单，可以做空逻辑
            if(Objects.nonNull(positionMap.get(stockCode))){
                /**
                 * 当日震幅计算>7.5 并且开盘价是高于收盘价的，说明空方力量强势
                 * 振幅 =（最高价 - 最低价）/ 昨日收盘价 × 100%
                 */
                Num upDownNum = (series.getBar(i).getHighPrice().minus(series.getBar(i).getLowPrice()))
                        .dividedBy(series.getBar(i).getClosePrice()).multipliedBy(series.numOf(100));
                boolean sellChooseUpDown = upDownNum.isGreaterThanOrEqual(series.numOf(7.5)) &&
                        series.getBar(i).getOpenPrice().isGreaterThan(series.getBar(i).getClosePrice());
                if ((strategy.shouldExit(i) && shortTermSMA.getValue(i).isGreaterThan(series.getBar(i).getClosePrice()))
                         || !isBullishArrangement || sellChooseUpDown
                         || midTermSMA.getValue(i).isGreaterThan(series.getBar(i).getClosePrice())) {
                    if(strategy.shouldExit(i)){
                        log.info("当前下降通道，上穿20时 索引: {} 生成卖出信号，价格：{},时间：{},WR指标值：{}",
                                i,series.getBar(i).getClosePrice(),dateStr,adjustedWRIndicator.getValue(i));
                    }
                    if(sellChooseUpDown){
                        log.info("========日期：{},当前价格：{},空方力量强势，卖出信号，当日振幅过大：{}",
                                dateStr,series.getBar(i).getClosePrice(),upDownNum);
                    }
                    sellTimes += 1;
                    anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.WR_SELL.getCode(), dateStr));
                    currentDateSignal = true;
                    BigDecimal amtVal = StockPositionVO.sell(positionMap.get(stockCode),series.getBar(i));
                    if(amtVal.compareTo(BigDecimal.ZERO)>0){
                        profitTimes += 1;
                    }
                    if(Objects.nonNull(positionMap.get(stockCode)) && positionMap.get(stockCode).getAmt().compareTo(profitLostStatVO.getAmt())>0){
                        profitLostStatVO.setAmt(positionMap.get(stockCode).getAmt());
                    }
                    positionMap.remove(stockCode);
                    amt = amt.add(amtVal);
                    System.out.println("=====================================");
                }
            }else{
                BigDecimal onceAmt = new BigDecimal(10000);
                boolean buy1 = strategy.shouldEnter(i) // WR下穿50 ~ 20 区间
                        && smaSlopeSM5.getValue(i).isGreaterThan(smaSlopeSM5.numOf(0.25)) //sma5 的斜率> 0.25
                        && smaSlopeSM10.getValue(i).isGreaterThan(smaSlopeSM10.numOf(0.1)) //sma10 的斜率> 0.1
                        && !longShadowIndicator.getValue(i) //不能有 长上影线
                        && shortTermSMA.getValue(i).isLessThanOrEqual(series.getBar(i).getClosePrice())  //sma5 < 当前价格
                        && isSlopePositive && isBullishArrangement; //均线排列  && 均线斜率向上
                if(buy1){
                    log.info("当前上升通道，下穿50时或者下穿20时 ,均线多头向上突破，double,  索引: {} 生成【买入】信号，价格：{},时间：{},WR指标值：{}",
                            i, series.getBar(i).getClosePrice(), dateStr, adjustedWRIndicator.getValue(i));
                }
                boolean buy2 = volumeRule.isSatisfied(i) && series.getBar(i).getClosePrice().isGreaterThan(series.getBar(i).getOpenPrice())
                        && shortTermSMA.getValue(i).isLessThanOrEqual(series.getBar(i).getClosePrice());
                if(buy2){
                    log.info("当前上升通道，当日出现历史天量 ,高于历史3天成交量的2倍, 立即生成【买入】信号，最新价格：{},时间：{},成交量：{}",
                            series.getBar(i).getClosePrice(), dateStr, series.getBar(i).getAmount());
                }
                if (buy1 || buy2){
                    onceAmt = onceAmt.multiply(new BigDecimal(2));
                    anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.WR_BUY.getCode(), dateStr));
                    currentDateSignal = true;
                    StockPositionVO positionVO = StockPositionVO.buy(stockCode, onceAmt, positionMap.get(stockCode), series.getBar(i));
                    positionMap.put(stockCode, positionVO);
                }
            }
            if(!currentDateSignal){
                anomalyDates.add(AnomalVO.me(AnomalyTypeEnum.NONE.getCode(),dateStr));
            }
        }
        profitLostStatVO.setStockCode(stockCode);
        profitLostStatVO.setTimes(sellTimes);
        profitLostStatVO.setProfitLostAmt(amt);
        if(sellTimes>0){
            BigDecimal successPercent = new BigDecimal(profitTimes).divide(new BigDecimal(sellTimes),2,RoundingMode.HALF_UP).multiply(new BigDecimal(100));
            log.info("### 股票：{}，卖出操作：{}次，盈利：{}次，合计总盈亏：{},胜率：{}%,最大持仓金额：{}",stockCode,
                    sellTimes,profitTimes,amt.doubleValue(),successPercent.doubleValue(),profitLostStatVO.getAmt());
        }
        return profitLostStatVO;
    }

    /**
     *  尾盘30分钟开板就卖空
     * */
    public static boolean isOpenBoardAtTail(List<StockMinuteData> minuteDataList) {
        boolean wasLimitUp = false; // 是否曾经涨停
        for (StockMinuteData data : minuteDataList) {
            // 判断是否在尾盘时间（14:30 至 15:00）
            if (data.getTime().isAfter(LocalTime.of(14, 30)) && data.getTime().isBefore(LocalTime.of(15, 0))) {
                if (data.getPrice() == data.getLimitUp()) {
                    // 如果当前价格等于涨停价，标记为曾涨停
                    wasLimitUp = true;
                } else if (wasLimitUp && data.getPrice() < data.getLimitUp()) {
                    // 如果曾涨停且当前价格小于涨停价，表示开板
                    return true;
                }
            }
        }
        // 未发现尾盘开板
        return false;
    }
    /**
     * 检查两条曲线是否越来越靠近并触发卖出信号
     * @param curveA    曲线 A（例如价格曲线）
     * @param curveB    曲线 B（例如指标曲线）
     * @param threshold 接近阈值
     * @return 如果满足卖出条件，返回 true；否则返回 false
     */
    public static boolean checkProximity(double preA,double curveA, double preB,double curveB, double threshold) {
        // 遍历时间序列，判断接近趋势
        double prevDiff = Math.abs(preA - preB);  // 上一个点的差值
        double currDiff = Math.abs(curveA - curveB); // 当前点的差值
        // 判断是否接近阈值且趋势表明继续靠近
        if (currDiff <= threshold && currDiff < prevDiff) {
            log.info("## LWR曲线小于阈值：{},预测可能即将在30内相交，触发卖出条件##",threshold);
            return true;
        }
        return false;
    }
}

